import os
import random
from functools import cache

import bpy
from bpy.props import BoolProperty, IntProperty, CollectionProperty, PointerProperty, StringProperty
from bpy.types import PropertyGroup as _Property

from .log import log
from .preferences import Preferences
from ..ops.asset_utils import AssetUtils as ImportAsset

prop_name = 'render_master'


class PropertyGroup(_Property,
                    Preferences,
                    ):
    """所有类继承属性"""
    is_import_asset: BoolProperty(name='是导入的资产',
                                  description='通用属性,如果为True 则为导入资产',
                                  default=False,
                                  )


class Scene(PropertyGroup):
    ...


class RenderProperty(PropertyGroup):
    """用于设置渲染输入时的路径"""
    name: str

    def update_cache(self, context):
        """删除缓存
        重新存
        TODO 所选资产
        TODO 所选资产选择项更新
        """
        self.name = self.path
        RenderProperty.__get_assets_items_path.cache_clear()

    render_matching_probability: IntProperty(name='渲染匹配概率',
                                             default=55,
                                             min=1,
                                             max=100,
                                             subtype='PERCENTAGE',
                                             )

    assets_path: StringProperty(name='替换资产的路径',
                                update=update_cache,
                                subtype='DIR_PATH')
    assets_tree: StringProperty(name='资产文件夹树',
                                update=update_cache,
                                )

    use_assets_path: BoolProperty(name='使用手动输入路径',
                                  update=update_cache,
                                  )

    search_children: BoolProperty(name='搜索子级目录',
                                  update=update_cache,
                                  )
    use_selected_asset: BoolProperty(name='使用所选资产',
                                     description='手动选择资产,如果未选择选择集将会选择活动集',
                                     update=update_cache,
                                     )

    selected_collect_name: StringProperty(name='选择集')

    @property
    def available_selected_collect(self) -> bool:
        """反回选择集是否可用的布尔值"""
        col = self.selected_collect_name
        return bool(col and (col in self.pref.selected_collect_items))

    @property
    def path(self) -> str:
        """反回实例的路径"""
        if self.use_assets_path:
            return self.assets_path
        return self.assets_tree

    @property
    def is_vail_path(self) -> bool:
        """反回路径是否可用的布尔值
        如果有资产数量也可以"""
        path = self.path
        # 如果没有路径或路径不是文件夹
        return path and os.path.isdir(path)

    @classmethod
    def _get_matching_file(cls, files: list[str]) -> 'list[str]':
        """获取匹配的文件"""
        return [file for file in files if os.path.splitext(file)[1].lower() == '.blend']

    @cache
    def __get_assets_items_path(self):
        """使用缓存
        避免每次调用都重新计算一次"""
        if not self.is_vail_path:  # 如果路径不可用则直接反回None
            return None

        items = []
        walk = list(os.walk(self.path))
        file_paths = (walk if self.search_children else walk[:1])
        for item in file_paths:
            matching = self._get_matching_file(item[2])
            for mat in matching:
                items.append(os.path.join(item[0], mat))
        return items

    @property
    def selected_collect(self) -> 'list[str]':
        """所选的选择集
        路径
        """
        name = self.selected_collect_name_(self.selected_collect_name)
        return list(item.asset_path for item in self.pref.active_objects if item.get_selected_collect_(name))

    @property
    def assets_items_path(self) -> list[str]:
        """反回目录下的的所有资产的路径
        只识别.blend
        选择集项
        """
        if self.use_selected_asset:
            if self.available_selected_collect:
                return self.selected_collect
            return self.selected_objects_path
        return RenderProperty.__get_assets_items_path(self)

    @property
    def assets_count(self) -> int:
        """反回实例资产的数量
        -1表示路径错误"""
        items = self.assets_items_path
        if items is None:
            return -1

        return len(self.assets_items_path)

    class Add(bpy.types.Operator):
        bl_idname = 'render.add_render_matching'
        bl_label = '添加渲染匹配设置'

        def execute(self, context):
            context.obj.render_matching.add()
            log.debug(f"添加渲染设置 {self}")
            return {'FINISHED'}

    class Del(bpy.types.Operator):
        bl_idname = 'render.del_render_matching'
        bl_label = '删除渲染匹配设置'

        index: IntProperty()

        def execute(self, context):
            context.obj.render_matching.remove(self.index)
            return {'FINISHED'}

    @classmethod
    def from_path_extract_name(cls, path: str) -> str:
        """从路径提取名称"""
        return os.path.splitext(os.path.split(path)[1])[0]

    def load_asset_collection(self, asset_path: 'str', data: dict) -> 'tuple[bpy.types.Collection,ImportAsset]':
        """加载资产的集合,
        如果存下来了的就直接用
        如果没有存就当场导入用
        """
        asset = ImportAsset()
        asset.asset_path = asset_path
        asset.asset_type = 'OBJECT'

        col = asset.from_file_load_collection()  # 导入资产作为集合
        data[self.id_data]['collection'][asset_path] = col
        return col, asset

    @property
    def preview_collection(self) -> 'bpy.types.Collection':
        """反回预览集合"""
        return ImportAsset.preview_collection_()

    def import_asset_to_location_box(self, location_box: 'bpy.types.Object', data: dict) -> str:
        """导入资产到输入的定位框
        从资产路径里面抽一个出来,
        如果资产路径没有就反回空字符串
        也就是不导入
        """
        if self.assets_items_path:
            asset_path = random.choice(self.assets_items_path)
            col, asset = self.load_asset_collection(
                asset_path, data)  # 将资产载入到blend
            log.debug(f'load  {col}  {location_box}  {asset_path}')

            asset.link_to_collect(col, self.preview_collection)
            asset.collection_translation_to_object(col, location_box)
            return self.from_path_extract_name(asset_path)
        return ''

    def draw(self, layout: 'bpy.types.UILayout', index: int):
        """绘制单个匹配项
        TAG-object 绘制项
        """
        pref = Preferences.pref_()
        sub_row = layout.split(factor=0.6, align=True)

        alert = (not self.is_vail_path) and (not self.assets_count)

        path_row = sub_row.row(align=True)
        path_row.alert = alert
        # Path Property
        prop_row = sub_row.row(align=True)
        if not self.use_selected_asset:
            prop_row.prop(self, 'use_assets_path',
                          text='',
                          icon='FILE_CACHE')
            prop_row.prop(self, 'search_children',
                          text='',
                          icon='CON_CHILDOF')
        else:
            prop_row.separator()
            prop_row.separator()

        prop_row.prop(self, 'use_selected_asset',
                      text='',
                      icon='RESTRICT_SELECT_OFF', )

        # Path
        if self.use_selected_asset:  # 使用所选资产
            path_row.label(text='使用所选项资产')
            ro = path_row.row()
            ro.alert = self.available_selected_collect
            ro.prop_search(self, 'selected_collect_name',
                           pref.asset_pointer.asset_items, 'asset_select_collect',
                           text='',
                           icon='RESTRICT_SELECT_OFF'
                           )

        elif self.use_assets_path:
            path_row.prop(self, 'assets_path',
                          text='',
                          icon='DOCUMENTS', )  # 使用资产路径
        else:
            path_row.prop_search(self, 'assets_tree',
                                 pref.asset_pointer.asset_tree, 'asset_object_tree',
                                 text='',
                                 icon='SEQ_STRIP_META'
                                 )

        # AssetCount
        count = self.assets_count
        co_row = prop_row.row()
        co_row.alert = (count == -1)
        co_row.label(text=str(count))

        ma = prop_row.row(align=True)
        ma.alert = alert
        ma.prop(self, "render_matching_probability", text="")
        ma.operator(RenderProperty.Del.bl_idname,
                    text="",
                    icon='X',
                    emboss=True,
                    ).index = index


class Object(PropertyGroup):
    render_matching: CollectionProperty(
        type=RenderProperty, name='渲染匹配参数', options={'ANIMATABLE', 'LIBRARY_EDITABLE', 'PROPORTIONAL'})

    @property
    def assets_items_path(self) -> list:
        """反回所有项的资产路径"""
        items = []
        for mat in self.render_matching:
            if mat and getattr(mat, 'assets_items_path', False):
                items.extend(mat.assets_items_path)
        return items

    def draw_render_matching(self, layout: 'bpy.types.UILayout'):
        """绘制渲染匹配项"""

        layout.context_pointer_set('obj', self)

        box = layout.box()
        split = box.split(factor=0.65)
        asset_col = split.column(align=True)
        count_col = split.column(align=True)
        match_col = split.column(align=True)

        row = asset_col.row(align=True)  # Padding
        row.separator()
        row.label(text="资产路径")

        row = count_col.row(align=True)  # Padding
        row.separator()
        row.label(text="资产数量")

        row = match_col.row(align=True)  # Padding
        row.separator()
        row.label(text="匹配系数")

        for index, match in enumerate(self.render_matching):
            match.draw(box, index)

        row = box.row()
        row.alignment = 'RIGHT'
        row.operator(RenderProperty.Add.bl_idname,
                     text="", icon='ADD', emboss=False)

    def import_asset(self, location_box: 'bpy.types.Object', data: dict) -> str:
        """导入资产
        从项里面抽一个
        """
        asset = self.random_get_matching_asset(data)
        if asset:
            return asset.import_asset_to_location_box(self.id_data, data)
        else:
            # 这个定位框没有资产
            return ''

    def random_get_matching_asset(self, data: dict) -> 'RenderProperty':
        """获取匹配资产
        随机从池里面抽一个出来
        """
        if self.id_data in data:
            items = data[self.id_data]
            if items and self.render_matching:
                # 有项
                asset_folder = self.render_matching[random.choice(
                    items['pool'])]
                return asset_folder


class Material(PropertyGroup):
    ...


class PoseBone(PropertyGroup):
    ...


class WindowManager(PropertyGroup):
    ...


class Collection(PropertyGroup):
    last_import_asset_path: StringProperty(name='上一次导入资产路径')


class Library(PropertyGroup):
    """
    库数据属性
    """


# TODO 添加方法向类
property_set_func = {
    bpy.types.Collection: {

    }
}

property_set_group = {
    bpy.types.Scene: {
        prop_name: PointerProperty(type=Scene, name='场景属性'),
    },
    bpy.types.Object: {
        prop_name: PointerProperty(type=Object, name='物体属性'),
    },
    bpy.types.Material: {
        prop_name: PointerProperty(type=Material, name='材质属性'),
    },
    bpy.types.PoseBone: {
        prop_name: PointerProperty(type=PoseBone, name='姿态骨骼属性'),
    },
    bpy.types.WindowManager: {
        prop_name: PointerProperty(type=WindowManager, name='窗口管理属性'),
    },
    bpy.types.Collection: {
        prop_name: PointerProperty(type=Collection, name='集合属性'),
    },
    bpy.types.Library: {
        prop_name: PointerProperty(type=Library, name='集合属性'),
    }
}

class_tuple = (
    # 提前注册
    RenderProperty,
    RenderProperty.Add,
    RenderProperty.Del,

    #
    Scene,

    Object,

    Library,

    Collection,

    Material,
    PoseBone,
    WindowManager,
)
register_class, unregister_class = bpy.utils.register_classes_factory(
    class_tuple)


def register():
    register_class()
    for typ, t in property_set_group.items():
        for attr, prop in t.items():
            if hasattr(typ, attr):
                log.debug(' * warning: overwrite ', typ, attr)
            setattr(typ, attr, prop)


def unregister():
    unregister_class()
    for typ, t in property_set_group.items():
        for attr in t.keys():
            if hasattr(typ, attr):
                delattr(typ, attr)
